﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using YasharEl.Infrastructure.Core.Prototype;
using YasharEl.Infrastructure.EntityModel;
using YasharEl.Infrastructure.ObjectFactory;

namespace YasharEl.Infrastructure.Core.EntityModel.Prototype
{
    public abstract class AbstractPrototypeEntity : Entity, IPrototypeEntity
    {
        public virtual IList<IPropertyInstance> Properties
        {
            get;
            private set;
        }

        public virtual IPropertyInstance StoreValue(string key, object value, IDependencyResolver resolver)
        {
            if (string.IsNullOrWhiteSpace(key))
                throw new ParameterInvalidKeyException();

            // Invalid configuration or usage of property
            IPropertyMapping mapping = MappingsContainer.DefinedProperties.FirstOrDefault((m) => m.PropertyDefinition.Key == key);
            if (mapping == null)
            {
                throw new PropertyResolutionException(key, GetType());
            }

            IDataTypeValueConverter valueConverter = DataTypeValueConverterFactory.CreateValueConverter(mapping.PropertyDefinition.DataType, resolver);

            // If required but not has value throw exception
            if (mapping.IsRequired && !valueConverter.HasValue(@value))
            {
                throw new MissingRequiredParameterValueException(mapping.PropertyDefinition.Key);
            }

            // If has value but value not valid throw type mismatch exception
            if (valueConverter.HasValue(@value) && !valueConverter.IsValid(@value))
            {
                throw new PropertyTypeMismatchException(
                    mapping.PropertyDefinition.Key, mapping.PropertyDefinition.DataType.ClrDataType
                );
            }

            if (Properties == null)
            {
                Properties = new List<IPropertyInstance>();
            }

            IPropertyInstance instance = Properties.SingleOrDefault(i => i.PropertyMapping.PropertyDefinition.Key == key);

            if (instance == null)
            {
                // If not created yet create new instance
                instance = CreatePropertyInstanceEntity();
                instance.PropertyMapping = mapping;
                instance.ValueReference = PropertyInstanceValueFactory.CreateValueReference(instance);
            }

            try
            {
                // Update value
                instance.ValueReference.RealValue = valueConverter.UnwrapRealValue(value);
            }
            catch (InvalidCastException)
            {
                throw new PropertyTypeMismatchException(
                    mapping.PropertyDefinition.Key,
                    valueConverter.UnwrapRealValue(value).GetType()
                );
            }

            ((AbstractPropertyInstanceEntity)instance).PrototypedEntity = this;

            Properties.Add(instance);

            return instance;
        }

        #region Abstract Methods

        protected abstract IHasPropertyDefinitions MappingsContainer { get; }
        protected abstract AbstractPropertyInstanceEntity CreatePropertyInstanceEntity();

        #endregion
    }
}
